Advanced
Advanced Effects
Section titled “Advanced Effects”Beyond the basic effect, the library provides advanced tools for managing effects lifecycle and subscribing to changes in different ways.
Effect Scope
Section titled “Effect Scope”effectScope creates a scope for multiple effects, allowing you to stop them all at once. This is useful when you need to manage multiple subscriptions together and dispose of them as a group.
import { signal, effect, effectScope } from '@nano_kit/store'
const $firstName = signal('John')const $lastName = signal('Doe')const $age = signal(30)
const stop = effectScope(() => { effect(() => { console.log('Name:', `${$firstName()} ${$lastName()}`) })
effect(() => { console.log('Age:', $age()) })})
/* All effects in the scope are stopped */stop()onMountEffect and onMountEffectScope
Section titled “onMountEffect and onMountEffectScope”onMountEffect runs an effect only when a mountable signal becomes active. This combines lifecycle management with reactive effects.
import { signal, mountable, onMountEffect } from '@nano_kit/store'
const $user = mountable(signal(null))const $userId = signal(1)
/* Effect runs only when $user is mounted */onMountEffect($user, () => { console.log('Fetching user:', $userId()) /* Effect re-runs when $userId changes */})onMountEffectScope runs an entire effect scope on mount:
import { signal, mountable, onMountEffectScope, effect } from '@nano_kit/store'
const $data = mountable(signal(null))const $status = signal('idle')
onMountEffectScope($data, () => { effect(() => console.log('Data changed:', $data())) effect(() => console.log('Status:', $status()))})Subscribe, Listen, and Observe
Section titled “Subscribe, Listen, and Observe”These functions provide different ways to react to signal changes:
subscribe - Calls the callback immediately and on every change. Triggers mount if the accessor is mountable.
import { subscribe } from '@nano_kit/store'
const stop = subscribe($count, (value) => { console.log('Current value:', value)})/* Called immediately with current value, then on every change */listen - Calls the callback only on changes, skipping the initial call. Triggers mount.
import { listen } from '@nano_kit/store'
const stop = listen($count, (value) => { console.log('Changed to:', value)})/* Called only when $count changes, not immediately */observe - Calls the callback only on changes, without triggering mount. Useful for passive observation.
import { observe } from '@nano_kit/store'
const $data = mountable(signal(0))
const stop = observe($data, (value) => { console.log('Observed:', value)})/* $data remains unmounted until something else mounts it */Complex Data Types
Section titled “Complex Data Types”When working with objects and arrays, the library provides tools to create reactive child signals for individual properties or elements. This enables fine-grained reactivity while keeping your code organized.
Record
Section titled “Record”record wraps an object signal and exposes its properties as individual signals. Each property becomes accessible through a $-prefixed accessor.
import { signal, record } from '@nano_kit/store'
const $user = signal({ name: 'Dan', age: 30 })const $userRecord = record($user)
/* Access properties as signals */console.log($userRecord.$name()) /* Dan */console.log($userRecord.$age()) /* 30 */
/* Update individual properties */$userRecord.$name('Alice')console.log($user()) /* { name: 'Alice', age: 30 } */record can also wrap plain objects:
const $user = record({ name: 'Dan', age: 30 })
$user.$name('Bob')Deep Record
Section titled “Deep Record”deepRecord recursively wraps nested objects, providing signals for properties at any depth.
import { deepRecord } from '@nano_kit/store'
const $user = deepRecord({ name: 'Dan', address: { city: 'Batumi', country: 'Georgia' }})
/* Access nested properties */console.log($user.$address.$city()) /* Batumi */
/* Update nested properties */$user.$address.$city('Tbilisi')List Operations
Section titled “List Operations”For arrays, use atIndex to create a signal for a specific element. The index can be static or dynamic.
import { signal, atIndex } from '@nano_kit/store'
const $users = signal(['Dan', 'John', 'Alice'])const $firstUser = atIndex($users, 0)
console.log($firstUser()) /* Dan */
/* Update through child signal */$firstUser('Bob')console.log($users()) /* ['Bob', 'John', 'Alice'] */Dynamic indexes:
const $index = signal(1)const $user = atIndex($users, $index)
console.log($user()) /* John */
$index(2)console.log($user()) /* Alice */Finding elements by predicate with atFoundIndex:
import { signal, atFoundIndex } from '@nano_kit/store'
const $users = signal([ { id: 1, name: 'Dan' }, { id: 2, name: 'John' }, { id: 3, name: 'Alice' }])
/* Select user with id 2 */const $targetUser = atFoundIndex($users, (user) => user.id === 2)
console.log($targetUser()) /* { id: 2, name: 'John' } */Additional list helpers:
push($list, ...values)- add elements to the endpop($list)- remove and return the last elementshift($list)- remove and return the first elementunshift($list, ...values)- add elements to the startsetIndex($list, index, value)- update element at indexdeleteIndex($list, index)- remove element at index
Object Operations
Section titled “Object Operations”atKey creates a signal for a specific key in an object. Works with both static and dynamic keys.
import { signal, atKey } from '@nano_kit/store'
const $userMap = signal({ 2: 'Dan', 4: 'John', 6: 'Alice'})
const $user4 = atKey($userMap, 4)console.log($user4()) /* John */
$user4('Bob')console.log($userMap()) /* { 2: 'Dan', 4: 'Bob', 6: 'Alice' } */Dynamic keys:
const $userId = signal(4)const $user = atKey($userMap, $userId)
$userId(6)console.log($user()) /* Alice */Additional object helpers:
setKey($object, key, value)- set a property valuedeleteKey($object, key)- remove a property
Signals Map
Section titled “Signals Map”SignalsMap is a reactive Map where each entry’s value is a signal.
import { type SignalsMap, $getMapKey, setMapKey, deleteMapKey, clearMap } from '@nano_kit/store'
const userMap: SignalsMap<number, User> = new Map()
/* Set entry */setMapKey(userMap, 1, { name: 'Dan', age: 30 })
/* Get entry reactively */effect(() => { console.log('User:', $getMapKey(userMap, 1))})
/* Delete entry */deleteMapKey(userMap, 1)
/* Clear all */clearMap(userMap)Functional Operators
Section titled “Functional Operators”Functional operators (fops) are pure functions that create accessors from other accessors or values. Unlike computed, they don’t memoize results — they recalculate on every access. For memoization, wrap them in computed.
These operators accept either static values or accessors, making them flexible for building derived state without immediate caching.
Logical Operations
Section titled “Logical Operations”import { signal, or, and, not, some, every } from '@nano_kit/store'
const $isAdmin = signal(false)const $isModerator = signal(true)
/* OR: returns first truthy value */const $hasPermissions = or($isAdmin, $isModerator)
/* AND: returns last value if all truthy */const $canEdit = and($isAdmin, $hasPermissions)
/* NOT: logical negation */const $isGuest = not($hasPermissions)
/* SOME: first truthy from multiple values */const $primaryRole = some($isAdmin, $isModerator, 'guest')
/* EVERY: last value if all truthy, otherwise first falsy */const $allChecks = every($isAdmin, $isModerator, $hasPermissions)Comparison Operations
Section titled “Comparison Operations”import { signal, is, isNot, gt, gte, lt, lte } from '@nano_kit/store'
const $age = signal(25)const $limit = signal(18)
/* Strict equality */const $isAdult = gte($age, 18)
/* Strict inequality */const $notEqual = isNot($age, $limit)
/* Comparisons */const $olderThanLimit = gt($age, $limit)const $atLeastLimit = gte($age, $limit)const $youngerThanLimit = lt($age, $limit)const $atMostLimit = lte($age, $limit)Conditional Operations
Section titled “Conditional Operations”import { signal, when } from '@nano_kit/store'
const $isLoggedIn = signal(true)const $username = signal('Alice')
/* Ternary: condition ? then : otherwise */const $greeting = when( $isLoggedIn, () => `Hello, ${$username()}`, 'Please log in')
console.log($greeting()) /* "Hello, Alice" */Memoization
Section titled “Memoization”Since fops don’t cache results, wrap them in computed for expensive operations:
import { signal, computed, and, gt } from '@nano_kit/store'
const $age = signal(25)const $hasLicense = signal(true)
/* Without memoization - recalculates on every access */const $canDrive = and(gt($age, 18), $hasLicense)
/* With memoization - caches until dependencies change */const $canDriveCached = computed($canDrive)Utilities
Section titled “Utilities”The library provides a set of utility functions for common tasks like rate limiting, value extraction, type checking, and working with signal properties.
Rate Limiting
Section titled “Rate Limiting”paced creates a proxy signal that updates the original signal using a rate limiter. This allows you to control how frequently a signal propagates updates while keeping immediate local updates. The proxy signal updates instantly, but the original signal updates only after the rate limiter allows it.
import { signal, paced, effect, debounce } from '@nano_kit/store'
const $search = signal('')const $searchPaced = paced($search, debounce(300))
effect(() => { console.log('Search:', $search())})
/* $searchPaced updates immediately, $search updates after 300ms */$searchPaced('a')$searchPaced('ab')$searchPaced('abc')/* Only logs "abc" after debounce completes */debounce and throttle are rate limiters that work with paced:
debounce delays updates until after a specified time has passed since the last change. Useful for expensive operations like search queries or API calls.
import { signal, paced, debounce } from '@nano_kit/store'
const $input = signal('')const $debouncedInput = paced($input, debounce(300))
/* Only the last value within 300ms window propagates to $input */throttle limits updates to once per time interval. First update executes immediately, subsequent updates are queued until the interval elapses.
import { signal, paced, throttle } from '@nano_kit/store'
const $scrollY = signal(0)const $throttledScroll = paced($scrollY, throttle(100))
/* Updates $scrollY at most once per 100ms */You can also use debounce and throttle standalone to wrap regular functions:
import { debounce, throttle } from '@nano_kit/store'
const performSearch = debounce(300)((query: string) => { console.log('Searching for:', query)})
const handleScroll = throttle(100)(() => { console.log('Scroll position:', window.scrollY)})Previous Value
Section titled “Previous Value”previous creates a computed that tracks the previous value of a signal. Returns undefined for the first read.
import { signal, previous, effect } from '@nano_kit/store'
const $count = signal(1)const $prevCount = previous($count)
effect(() => { console.log(`Changed from ${$prevCount()} to ${$count()}`)})
$count(2) /* Changed from 1 to 2 */$count(3) /* Changed from 2 to 3 */Computed Properties
Section titled “Computed Properties”length creates a computed for the length property of arrays or strings.
import { signal, length, effect } from '@nano_kit/store'
const $items = signal(['a', 'b', 'c'])const $itemCount = length($items)
console.log('Count:', $itemCount()) /* Count: 3 */boolean converts a signal’s value to a boolean.
import { signal, boolean } from '@nano_kit/store'
const $user = signal(null)const $hasUser = boolean($user)
console.log($hasUser()) /* false */$user({ name: 'Dan' })console.log($hasUser()) /* true */concat concatenates multiple values or accessors into a string.
import { signal, concat, effect } from '@nano_kit/store'
const $firstName = signal('John')const $lastName = signal('Doe')const $fullName = concat($firstName, ' ', $lastName)
effect(() => { console.log($fullName())})
$firstName('Jane') /* Jane Doe */Type Checking
Section titled “Type Checking”isFunction checks if a value is a function.
import { isFunction } from '@nano_kit/store'
isFunction(() => {}) /* true */isFunction(42) /* false */isAccessor checks if a value is an accessor (function).
import { isAccessor } from '@nano_kit/store'
const $value = () => 42
isAccessor($value) /* true */isAccessor(42) /* false */isSignal checks if a value is a signal.
import { signal, computed, isSignal } from '@nano_kit/store'
const $count = signal(0)const $doubled = computed(() => $count() * 2)const $accessor = () => 42
isSignal($count) /* true */isSignal($doubled) /* true */isSignal($accessor) /* false */Value Extraction
Section titled “Value Extraction”$get extracts a value from either a plain value or an accessor.
import { signal, $get } from '@nano_kit/store'
const $count = signal(5)
$get($count) /* 5 */$get(10) /* 10 */$get(() => 15) /* 15 */toSignal converts a value, accessor, or signal into a writable signal.
import { signal, computed, toSignal } from '@nano_kit/store'
const $a = toSignal(42) /* Creates signal(42) */const $b = toSignal(signal(10)) /* Returns input signal */const $c = toSignal(computed(() => 5)) /* Returns input computed */const $d = toSignal(() => 20) /* Creates computed(() => 20) */toAccessor converts a value into an accessor, or returns the accessor if already a function.
import { signal, computed, toAccessor } from '@nano_kit/store'
const $a = toAccessor(42) /* Сreates () => 42 */const $b = toAccessor(signal(10)) /* Returns input signal */const $c = toAccessor(computed(() => 5)) /* Returns input computed */const $d = toAccessor(() => 20) /* Returns input accessor */toAccessorOrSignal converts a value into a signal, or returns the accessor/signal if already a function.
import { signal, computed, toAccessorOrSignal } from '@nano_kit/store'
const $a = toAccessorOrSignal(42) /* Creates signal(42) */const $b = toAccessorOrSignal(signal(10)) /* Returns input signal */const $c = toAccessorOrSignal(computed(() => 5)) /* Returns input computed */const $d = toAccessorOrSignal(() => 20) /* Returns input accessor */Cleanup Composition
Section titled “Cleanup Composition”composeDestroys combines multiple cleanup functions into a single function.
import { effect, composeDestroys } from '@nano_kit/store'
const cleanup1 = () => console.log('Cleanup 1')const cleanup2 = () => console.log('Cleanup 2')
const stop = effect(() => { /* ... */ return composeDestroys(cleanup1, cleanup2)})
stop() /* Logs: Cleanup 1, Cleanup 2 */